package com.loklok.confuse.util;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.loklok.confuse.extension.PathConfigSettingCache;
import com.loklok.confuse.model.CodePathConfig;
import com.loklok.confuse.model.ConfuseConfig;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.log.NullLogChute;
import org.jetbrains.annotations.NotNull;

import java.io.StringWriter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Classname VelocityUtils
 * @Description
 * @Date 2023/5/8 17:00
 * @Created by wangchangjiu
 */
public class VelocityUtils {


    /**
     * 执行 生成代码
     *
     * @param anActionEvent
     * @param project
     * @param config
     * @param name
     * @param loadCodeConfigItem
     */
    public static void doGenerateCode(AnActionEvent anActionEvent, Project project, CodePathConfig config, String name,
                                      ConfuseConfig.LoadCodeConfigItem loadCodeConfigItem) {
        if (loadCodeConfigItem.configIsController()) {
            generateControllerCode(anActionEvent, project, config, name, loadCodeConfigItem);
        } else if (loadCodeConfigItem.configIsRequest()) {
            generateRequestCode(anActionEvent, project, config, name, loadCodeConfigItem);
        } else if (loadCodeConfigItem.configIsVo()) {
            generateVoCode(anActionEvent, project, config, name, loadCodeConfigItem);
        } else if (loadCodeConfigItem.configIsMethod()) {
            generateMethodCode(anActionEvent, project, config, name, loadCodeConfigItem);
        }
    }

    private static void generateMethodCode(AnActionEvent anActionEvent, Project project, CodePathConfig config, String name, ConfuseConfig.LoadCodeConfigItem loadCodeConfigItem) {

        Editor editor = anActionEvent.getData(CommonDataKeys.EDITOR);

        Document document = editor.getDocument();
        Caret currentCaret = editor.getCaretModel().getPrimaryCaret();
        // 插入代码到光标位置
        int offset = currentCaret.getOffset();


        JSONObject controllerInfo = new JSONObject();
        controllerInfo.put("classPrefix", CommonUtil.pckToClassPrefix(name));
        controllerInfo.put("methods", loadCodeConfigItem.getMethods());
        VelocityContext velocityContext = new VelocityContext();
        velocityContext.put("controllerInfo", controllerInfo);
        String templatePath = CommonUtil.pathJoint(config.getCodeTemplate() ,loadCodeConfigItem.getLoadPath());
        String generateCode = VelocityUtils.generateCode(velocityContext, templatePath, loadCodeConfigItem.getClassName());

        WriteCommandAction.runWriteCommandAction(project, () -> {
            try {
                document.insertString(offset, generateCode);
                // 格式化插入的代码
                PsiFile psiFile = anActionEvent.getData(CommonDataKeys.PSI_FILE);
                PsiDocumentManager.getInstance(project).commitDocument(document);
                CodeStyleManager.getInstance(project).reformatText(psiFile, offset, offset + generateCode.length());
            } catch (Exception ex) {
                // 弹框提示
                Messages.showMessageDialog(ex.getMessage(), "error massage:", Messages.getErrorIcon());
            }
        });
    }


    /**
     * 生成 VO 代码
     *
     * @param anActionEvent
     * @param project
     * @param config
     * @param name
     * @param loadCodeConfigItem
     */
    private static void generateVoCode(AnActionEvent anActionEvent, Project project, CodePathConfig config, String name,
                                       ConfuseConfig.LoadCodeConfigItem loadCodeConfigItem) {
        JSONObject vo = new JSONObject();
        putCommonData(config.getVoPath(), config.getCodeAuthor(), name, vo);

        String vmTemplatePath = CommonUtil.pathJoint(config.getCodeTemplate() , loadCodeConfigItem.getLoadPath(), loadCodeConfigItem.getClassName()) + ".vm";
        fillSynonym(config, vmTemplatePath, vo, 1);

        vo.put("importPackage", new HashSet<String>() {{
            add(CommonUtil.pathToImportPackage(CommonUtil.pathJoint(config.getRequestPath() , name)));
        }});
        VelocityContext velocityContext = new VelocityContext();
        velocityContext.put("vo", vo);

        String templatePath = CommonUtil.pathJoint(config.getCodeTemplate() , loadCodeConfigItem.getLoadPath());
        String generateCode = VelocityUtils.generateCode(velocityContext, templatePath, loadCodeConfigItem.getClassName());
        LocalFileUtils.writeFile(anActionEvent, project, CommonUtil.pathToPackage(CommonUtil.pathJoint(config.getVoPath(),name)), CommonUtil.pckToClassPrefix(name) + loadCodeConfigItem.getClassName(), generateCode, JavaFileType.INSTANCE);
    }

    /**
     * 生成 Request 代码
     *
     * @param anActionEvent
     * @param project
     * @param config
     * @param name
     * @param loadCodeConfigItem
     */
    private static void generateRequestCode(AnActionEvent anActionEvent, Project project, CodePathConfig config,
                                            String name, ConfuseConfig.LoadCodeConfigItem loadCodeConfigItem) {
        JSONObject request = new JSONObject();
        putCommonData(config.getRequestPath(), config.getCodeAuthor(), name, request);

        String vmTemplatePath = CommonUtil.pathJoint(config.getCodeTemplate() , loadCodeConfigItem.getLoadPath(), loadCodeConfigItem.getClassName()) + ".vm";
        fillSynonym(config, vmTemplatePath, request, 0);

        request.put("importPackage", new HashSet<String>() {{
            add(CommonUtil.pathToImportPackage(CommonUtil.pathJoint(config.getVoPath() , name)));
        }});
        VelocityContext velocityContext = new VelocityContext();
        velocityContext.put("request", request);

        String templatePath = CommonUtil.pathJoint(config.getCodeTemplate(), loadCodeConfigItem.getLoadPath());
        String generateCode = VelocityUtils.generateCode(velocityContext, templatePath, loadCodeConfigItem.getClassName());
        LocalFileUtils.writeFile(anActionEvent, project, CommonUtil.pathToPackage(CommonUtil.pathJoint(config.getRequestPath() , name)), CommonUtil.pckToClassPrefix(name) + loadCodeConfigItem.getClassName(), generateCode, JavaFileType.INSTANCE);

    }

    public static void generateDtoVoCode(AnActionEvent anActionEvent, Project project, CodePathConfig config, String voPath, String pckName, boolean isChooseVo) {

        String contextKey;
        String importPackagePath;
        String pathToPackage;
        if(isChooseVo){
            contextKey = "vo";
            importPackagePath = config.getRequestPath();
            pathToPackage = config.getVoPath();
        } else {
            contextKey = "request";
            importPackagePath = config.getVoPath();
            pathToPackage = config.getRequestPath();
        }

        JSONObject vo = new JSONObject();
        putCommonData(pathToPackage, config.getCodeAuthor(), pckName, vo);

        fillSynonym(config, voPath, vo, 1);

        String finalImportPackagePath = importPackagePath;
        vo.put("importPackage", new HashSet<String>() {{
            add(CommonUtil.pathToImportPackage(CommonUtil.pathJoint(finalImportPackagePath, pckName)));
        }});
        VelocityContext velocityContext = new VelocityContext();
        velocityContext.put(contextKey, vo);

        // E://A/d/EE.java.vm
        Path path = Paths.get(voPath);

        // 获取目录路径
        Path directoryPath = path.getParent();

        // 获取文件名
        String className = path.getFileName().toString().replace(".vm", "");
        String generateCode = VelocityUtils.generateCode(velocityContext, directoryPath.toString(), className);
        LocalFileUtils.writeFile(anActionEvent, project, CommonUtil.pathToPackage(CommonUtil.pathJoint(pathToPackage, pckName)), CommonUtil.pckToClassPrefix(pckName) + className, generateCode, JavaFileType.INSTANCE);
    }

    private static void fillSynonym(CodePathConfig config, String templatePath, JSONObject request, int type) {
        Set<String> paramNames = parseParams(templatePath, type);

        if(CollectionUtil.isEmpty(paramNames)){
            return;
        }

        Map<String, Set<String>> synonym = new HashMap<>();
        if(paramNames.size() > config.getBatch()){
             Lists.partition(new ArrayList<>(paramNames), config.getBatch())
                    .stream()
                    .map(HashSet::new)
                    .collect(Collectors.toList()).forEach(partitionParamNames -> synonym.putAll(doDeepSeekGetSynonym(config, partitionParamNames)));
        } else {
            synonym.putAll(doDeepSeekGetSynonym(config, paramNames));
        }


        synonym.forEach((key, val) -> {
            String randomElement = val.stream()
                    .skip(new Random().nextInt(val.size()))
                    .findFirst().get();
            randomElement = randomElement.replaceAll(" ", "");
            request.put(key, randomElement);
        });
    }

    private static Map<String, Set<String>> doDeepSeekGetSynonym(CodePathConfig config, Set<String> paramNames) {
        String prompt = String.format(config.getDeepSeekPrompt(), String.join(",", paramNames));
        Map<String, Set<String>> synonym = new HashMap<>();
        try {
            synonym = OpenAIUtil.getSynonym(config.getDeepSeekApiKey(), prompt);
        } catch (Exception e) {
            ApplicationManager.getApplication().invokeLater(() ->  Messages.showMessageDialog("deep seek 获取近似词失败,请检查账号可用性或者每次获取近义词的个数过多", "error massage:", Messages.getErrorIcon()));
        }
        return synonym;
    }

    @NotNull
    private static Set<String> parseParams(String templatePath,  int type) {
       // String templatePath = CommonUtil.pathJoint(config.getCodeTemplate() , loadCodeConfigItem.getLoadPath(), loadCodeConfigItem.getClassName()) + ".vm";
        String readString = LocalFileUtils.fileReadString(templatePath);
        Set<String> paramNames = CommonUtil.regularExtractParams(readString, type);
        paramNames.removeAll(Arrays.asList("pck","importPackage","classPrefix"));
        return paramNames;
    }

    private static void generateControllerCode(AnActionEvent anActionEvent, Project project, CodePathConfig config, String name,
                                               ConfuseConfig.LoadCodeConfigItem loadCodeConfigItem) {
        JSONObject controllerInfo = new JSONObject();
        putCommonData(config.getControllerPath(), config.getCodeAuthor(), name, controllerInfo);
        controllerInfo.put("path", name);
        controllerInfo.put("importPackage", new HashSet<String>() {{
            add(CommonUtil.pathToImportPackage(CommonUtil.pathJoint(config.getRequestPath(), name)));
            add(CommonUtil.pathToImportPackage(CommonUtil.pathJoint(config.getVoPath() , name)));
        }});

        controllerInfo.put("methods", loadCodeConfigItem.getMethods());
        VelocityContext velocityContext = new VelocityContext();
        velocityContext.put("controllerInfo", controllerInfo);
        String templatePath = CommonUtil.pathJoint(config.getCodeTemplate() ,loadCodeConfigItem.getLoadPath());
        String generateCode = VelocityUtils.generateCode(velocityContext, templatePath, loadCodeConfigItem.getClassName());
        LocalFileUtils.writeFile(anActionEvent, project, CommonUtil.pathToPackage(CommonUtil.pathJoint(config.getControllerPath() , name)), CommonUtil.pckToClassPrefix(name) + loadCodeConfigItem.getClassName(), generateCode, JavaFileType.INSTANCE);
    }

    private static void putCommonData(String configPath, String codeAuthor, String name, JSONObject jsonObject) {
        jsonObject.put("pck", CommonUtil.pathToDeclarePackage(configPath) + "." + name);
        jsonObject.put("classPrefix", CommonUtil.pckToClassPrefix(name));
        jsonObject.put("author", codeAuthor);
    }


    @NotNull
    public static String generateCode(VelocityContext velocityContext, String templatePath, String fileName) {
        final ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(VelocityUtils.class.getClassLoader());

        //实例化
        VelocityEngine velocityEngine = new VelocityEngine();

        //关闭日志
        velocityEngine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new NullLogChute());
       // velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
       // velocityEngine.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
        // 设置资源加载器为 FileResourceLoader
        velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "file");

        // 设置资源加载器的路径
        //velocityEngine.setProperty(FileResourceLoader.FILE_RESOURCE_LOADER_PATH, "/path/to/templates");
        velocityEngine.setProperty("file.resource.loader.path", templatePath);
        velocityEngine.init();

        StringWriter writer = new StringWriter();
        //生成代码
        velocityEngine.getTemplate(fileName + ".vm", "UTF-8").merge(velocityContext, writer);

        String code = writer.toString();

        Thread.currentThread().setContextClassLoader(oldContextClassLoader);
        return code.replaceAll("\r\n", "\n");
    }


}
